/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "XMInstrument.h"
#include <resource/Resource.h>
#include "EnvelopeBuilder.h"
#include "OrderedReader.h"
#include "XMPlayer.h"

#include <boost/scoped_ptr.hpp>

#ifdef SAMPLE_WRITER
#include "SaveWave.h"
#endif

#ifdef EXTERN_SAMPLES
#include <noise/DataSource.h>
#include <noise/Manager.h>
#endif

// Let filenames looks better without spaces before and after...
static std::string make_not_so_bad_name(std::string name)
{
	int null_pos = name.find( char(0), 0 );
	if( null_pos != std::string::npos )
		name.resize( null_pos );
	while( !name.empty() )
	{
		if( isspace( name[0] ) )
			name.erase( 0, 1 );
		else
		if( isspace( name[name.size()-1] ) )
			name.resize( name.size() - 1 );
		else
			break;
	}
	return name;
}


XMInstrument::XMSample::XMSample( resource::Resource * resource, const std::string & instrument_name )
:	data( 0 ),
	instrument_name( instrument_name )
{
	//sample length
	sample_length = read_long_little_endian( resource );

	//sample loop start
	loop_start = read_long_little_endian( resource );

	//sample loop length
	loop_length = read_long_little_endian( resource );

	//volume
	resource->read( &volume, 1 );

	//finetune
	resource->read( &fine_tune, 1 );

//	type
//	Bit 0-1: 0 = No loop, 1 = Forward loop,
//	2 = Ping-pong loop;
//	4 = 16-bit sampledata

	resource->read( &type, 1 );

	//panning
	resource->read( &panning, 1 );

	//relative note number
	resource->read( &relative_note, 1 );

	//reserved
	resource->skip( 1 );

	//sample name
	char name_buf[22];
	resource->read( name_buf, 22 );
	name = name_buf;

	name = make_not_so_bad_name( name );

	#ifdef SAMPLE_WRITER
	filesystem::String file_name = "samples/";
	file_name += instrument_name;
	file_name += "_";
	file_name += name.c_str();
	printf( "Sample name: {%s}\n", file_name.c_str() );
	#endif
}

XMInstrument::XMSample::~XMSample()
{
	delete [] data; data = 0;
}

void XMInstrument::XMSample::read_sample_data( resource::Resource * resource )
{
	if( get_length() > 0 )
	{
		data = new short int[ (get_length() + 1)/2 + 1 ];	//just for good alignment
		if( !data )
			return;

		memset( data, 0, get_length() );

		#ifdef SAMPLE_WRITER
		int sample_data_start_pos = resource->get_position();
		#endif

		#ifdef EXTERN_SAMPLES
		filesystem::String file_name = "samples/";
		file_name += instrument_name.c_str();
		file_name += "_";
		file_name += name.c_str();

		boost::scoped_ptr< noise::DataSource > data_source(noise::Manager::create_datasource( file_name ));
		if( data_source )
		{
			data_source->decode( get_length()/(type & 0x10 ? 2 : 1) );

			//workaround for mac, using only 16-bit encoding
			bool modified_length = false;
			if(!(type&0x10))
			{
				sample_length *= 2;
				loop_start *= 2;
				loop_length *= 2;

				delete [] data; data = 0;

				data = new short int[ (get_length() + 1)/2 + 1 ];	//just for good alignment
				if( !data )
					return;

				memset( data, 0, get_length() );
				type |= 0x10;
				modified_length = true;
			}

			data_source->get_data( noise::DataSource::MONO, data, (type & 0x10) ? 2 : 1, (type & 0x10) ? 16 : 8  );
			if( type & 0x10 )
			{
				short int * temp_data = (short int *)data;
				int k = get_length()/2;
				temp_data[k] = temp_data[k-1]; //for loop conditions and interpolation
			}
			else
			{
				signed char * temp_data = (signed char *)data;
				int k = get_length();
				temp_data[k] = temp_data[k-1]; //for loop conditions and interpolation

				//XMPlayer works with signed data
				for ( k = 0; k <= get_length(); ++k )
				{
					temp_data[k] -= 0x80;
				}
			}
			resource->skip( modified_length ? get_length() / 2 : get_length() );
		}
		else	//no data source, trying to look for them in the xm module
		#endif
		if( !(type & 0x10) )	//8-bit sample, converting to signed data
		{
			#ifdef SAMPLE_WRITER
			printf( "Warning 8-bit sample\n" );
			#endif

			signed char * temp_data = (signed char *)data;

			signed char current_byte;
			signed char delta_byte;

			resource->read( &current_byte, 1 );
			temp_data[0] = current_byte;

			int k;
			for ( k = 1; k < get_length(); ++k )
			{
				resource->read( &delta_byte, 1 );
				current_byte += delta_byte;
				temp_data[k] = current_byte;
			}
			temp_data[k] = temp_data[k-1]; //for loop conditions and interpolation
		}
		else
		{
			short int * temp_data = (short int *)data;

			short int current_val = read_short_little_endian( resource );
			short int delta_val;

			temp_data[0] = current_val;

			int k;
			for ( k = 1; k < get_length()/2; ++k )
			{
				delta_val = read_short_little_endian( resource );
				current_val += delta_val;
				temp_data[k] = current_val;
			}
			temp_data[k] = temp_data[k-1]; //for loop conditions and interpolation

			//strange but already happened
			resource->skip( get_length() - (get_length()/2)*2 );
		}

		#ifdef SAMPLE_WRITER
		{
		filesystem::String file_name = "samples/";
		file_name += instrument_name.c_str();
		file_name += "_";
		file_name += name.c_str();
		file_name += ".wav";
		save_wave( file_name, Format( 22050, 1, (type & 0x10) ? 16 : 8 ), data, get_length() );
		memset( (char*)xm_data_for_sample_writer + sample_data_start_pos, 0, resource->get_position() - sample_data_start_pos );
		}
		#endif

		sample_length /= get_sample_size();
		loop_start /= get_sample_size();
		loop_length /= get_sample_size();
	}
}

XMInstrument::XMInstrument( resource::Resource * resource )
{
	unsigned instrument_header_size = read_long_little_endian( resource );
	//get header size

	//get instrument name
	char name_buf[22];
	resource->read( name_buf, 22 );
	name = name_buf;
	name = make_not_so_bad_name( name );

	unsigned char type; //Instrument type (always 0, as documentation says)
	//get instrument type
	resource->read( &type, 1 );

	//get number of samples in the instrument
	unsigned short number_of_samples = read_short_little_endian( resource );

	if( number_of_samples == 0 )
	{
		resource->skip( instrument_header_size - 29 );
		return;
	}

	//reading samples
	unsigned sample_header_size = read_long_little_endian( resource );

	//get sample number for each note
	sample_number.reserve( 96 );
	int i;
	for( i = 0; i < 96; ++i )
	{
		unsigned char sn;
		resource->read( &sn, 1 );
		sample_number.push_back( sn );
	}

	unsigned short volume_temp_points[24];
	//get volume envelope points
	for( i = 0; i < 24; ++i )
		volume_temp_points[i] = read_short_little_endian( resource );

	//get panning envelope points
	unsigned short panning_temp_points[24];
	for( i = 0; i < 24; ++i )
		panning_temp_points[i] = read_short_little_endian( resource );

	//get number of volume points
	unsigned char points_count;
	resource->read( &points_count, 1 );

	EnvelopePoints volume_envelope_points;
	volume_envelope_points.reserve( points_count );
	for( i = 0; i < points_count; ++i )
		volume_envelope_points.push_back( EnvelopePoint( volume_temp_points[i*2], volume_temp_points[i*2+1] ) );

	EnvelopeBuilder volume_envelope_builder;
	volume_envelope_builder.make_points( volume_envelope_points );

	//get number of pan points
	resource->read( &points_count, 1 );

	EnvelopePoints panning_envelope_points;
	panning_envelope_points.reserve( points_count );
	for( i = 0; i < points_count; ++i )
		panning_envelope_points.push_back( EnvelopePoint( panning_temp_points[i*2], panning_temp_points[i*2+1] ) );

	EnvelopeBuilder panning_envelope_builder;
	panning_envelope_builder.make_points( panning_envelope_points );

	//get volulme sustain point
	unsigned char sustain_point;
	resource->read( &sustain_point, 1 );
	volume_envelope_builder.make_sustain_point( sustain_point );

	//get volulme loop start point
	unsigned char loop_begin;
	resource->read( &loop_begin, 1 );
	volume_envelope_builder.make_loop_begin( loop_begin );

	//get volume loop end point
	unsigned char loop_end;
	resource->read( &loop_end, 1 );
	volume_envelope_builder.make_loop_end( loop_end );

	//get pan sustain point
	resource->read( &sustain_point, 1 );
	panning_envelope_builder.make_sustain_point( sustain_point );

	//get pan loop start point
	resource->read( &loop_begin, 1 );
	panning_envelope_builder.make_sustain_point( loop_begin );
	
	//get pan loop end point
	resource->read( &loop_end, 1 );
	panning_envelope_builder.make_sustain_point( loop_end );

	//get volume envelope type
	unsigned char envelope_type;
	resource->read( &envelope_type, 1 );
	volume_envelope_builder.make_envelope_type( envelope_type );

	volume_envelope = volume_envelope_builder.get_result();

	//get panning envelope type
	resource->read( &envelope_type, 1 );
	panning_envelope_builder.make_envelope_type( envelope_type );

	panning_envelope = panning_envelope_builder.get_result();

	//get vibrato type
	resource->read( &vibrato_type, 1 );

	//get vibrato sweep
	resource->read( &vibrato_sweep, 1 );

	//get vibrato depth
	resource->read( &vibrato_depth, 1 );

	//get vibrato rate
	resource->read( &vibrato_rate, 1 );

	//get volume fadeout
	volume_fadeout = read_short_little_endian( resource );

	//skip reserved bytes
	resource->skip( instrument_header_size - 241 );

	//reading samples
	samples.reserve( number_of_samples );
	int v;
	for( v = 0; v < number_of_samples; ++v )
	{
		XMSample * sample = new XMSample( resource, name );
		samples.push_back( sample );
	}

	for( v = 0; v < number_of_samples; ++v )
		samples[v]->read_sample_data( resource );
}

XMInstrument::~XMInstrument()
{
	for( unsigned i = 0; i < samples.size(); ++i )
	{
		delete samples[i]; samples[i] = 0;
	}
}

